
應用地端 AI 領域中,一款開放權重模型出廠時,已經用大量語言資料訓練過,它會的東西叫「預訓練知識」。「微調」則是在這個基礎上,用一小份你自己的資料再訓練這款模型一次,讓它學會某種特定的行為,例如固定的輸出格式、某種語氣、某個領域的用語、視覺風格、特定人物的臉等等,或今天要教的「用哪種語言思考」。在過往一般預訓練一款模型花的是幾百萬美元與幾個月的時間成本,微調花的則是幾百到幾千筆、幾萬筆資料與幾十分鐘到幾小時,兩者不是同一個等級的事。
微調有三種常見做法,差別在動了多少權重:
全參數微調
整個模型的權重都更新。效果最直接,但 27B 模型的權重、梯度與優化器狀態加起來要幾百 GB 記憶體,這台機器做不到,一般人也不需要。
LoRA
把原始權重凍結不動,在旁邊掛一組很小的低秩矩陣(rank 通常 8 到 64),只訓練這組旁路。訓練完的產物叫 adapter,通常只有幾十到幾百 MB,可以合併回底模,也可以掛著用。今天訓練的就是它,程式碼裡那個 r=16 就是 rank。
QLoRA
LoRA 的省記憶體版,先把底模量化成 4-bit 載入(Day 12 文章中我講過的那套量化在這裡出現了),再掛 LoRA 訓練。27B 的底模從 56 GB 縮成約 17 GB,這是 128GB 機器能舒服跑 27B 微調的原因,也是今天的實際做法。
那什麼時候該微調、什麼時候該用提示詞就好?我這邊提出一個實用的分界,如果提示詞每一輪都要重付,微調付一次。在規則少、偶爾用,這種情況下,寫在提示詞或 AGENTS.md 裡就夠了。
假設你的應用,規則多到吃掉上下文、每一輪都要重申、或希望它變成模型的「習慣」而不是「被提醒才做」,那就是微調的場域。
反過來說,微調不擅長塞入大量新事實(那是 RAG 的工作),也改不了預訓練刻進去的深層傾向 (這邊我姑且稱之為本性難移),今天的結果會再證明這句話一次。
系列走到第 25 天,模型測過六款、推論引擎跑過五個,該回答一個讀者一直在問的問題,128GB 統一記憶體除了推論,能不能拿來訓練?答案是當然能,而且它的甜蜜點很明確,主要會是 27B 級模型的 QLoRA 微調,這個等級的模型在 24GB VRAM 的顯卡上要靠各種卸載技巧硬撐,但是呢,在 GB10 這台機器上是剛剛好,可說是訓練專用機。
而更大參數的模型,這台同樣也可以微調,但就會再搭配量化技術才行。
以下是常見的主流模型微調所需要的記憶體相關成本比較 :
| 參數量級與代表模型 | BF16 底模大小 | 全參數微調 (16-bit) | LoRA 微調 (16-bit 基底) | QLoRA 微調 (4-bit 基底) | 建議硬體門檻 |
|---|---|---|---|---|---|
| 3B ~ 4B(Llama 3.2 3B, Qwen 2.5 3B) | 約 6 ~ 8 GB | 約 45 ~ 50 GB | 約 9 ~ 11 GB | 約 5 ~ 7 GB | 單張 8GB ~ 12GB 消費級顯示卡(如 RTX 4060) |
| 7B ~ 9B(Llama 3.1 8B, Qwen 2.5 7B, Gemma 2 9B) | 約 14 ~ 18 GB | 約 120 ~ 140 GB | 約 20 ~ 24 GB | 約 10 ~ 14 GB | 單張 16GB 顯示卡(如 RTX 4080)或 24GB 顯示卡(RTX 3090/4090) |
| 14B(Qwen 2.5 14B) | 約 28 GB | 約 220 ~ 250 GB | 約 34 ~ 40 GB | 約 15 ~ 18 GB | 單張 24GB 顯示卡(RTX 3090/4090)或 32GB 統一記憶體設備 |
| 27B ~ 32B(Gemma 2 27B, Qwen 2.5 32B) | 約 54 ~ 64 GB | 約 450 ~ 550 GB | 約 65 ~ 75 GB | 約 22 ~ 28 GB | 單張 24GB 顯示卡(需限制長度)、雙卡、或 64GB/128GB 統一記憶體設備 |
| 70B ~ 72B(Llama 3.3 70B, Qwen 2.5 72B) | 約 140 ~ 144 GB | 約 1.1 ~ 1.2 TB | 約 160 ~ 180 GB | 約 48 ~ 56 GB | 雙張 48GB 專業卡、4 張 24GB 顯示卡、或 128GB 統一記憶體設備 |
更大參數的模型,需要大量 AI 運算叢集來訓練和微調,就不是一般公司與普通學校設備能負擔得起的了。
因此今天的微調目標,我不想用「情感分類」這種教科書範例,要用一個本系列文章的發現的一個痛點來做測試。Day 14 發現 Qwen3.8-27B 思考時用簡體中文,最終答案才切回繁體,做繁中評測時這件事會讓判準失真,實際使用時思考內容一旦露出來也很違和。另外,我自己寫部落格優格網的時候有一套自己的編輯規範,比方說不用破折號、不用分號、frontier 一律譯為尖端或先進,這些規則模型天生不會,如果要測試使用,變成得靠提示詞去規範或 agent.md 來管理。
所以今天的目標是雙重的:讓它用繁體中文思考,讓它內建我在部落格的編輯規範。 兩個目標都可以用機器測試數字來驗收前後對比,而不是只靠感覺體會。
Day 6 預告過 Unsloth 在 GB10 的 ARM64 環境要留意 torchcodec 的 wheel 相依。今天親自來試驗一遍看看,結果跟預期相反,那款地雷不存在,而且整個安裝零編譯、98 秒結束。

uv venv unsloth && source unsloth/bin/activate
export CUDACXX=/usr/local/cuda/bin/nvcc CUDA_HOME=/usr/local/cuda # Day 24 那款地雷,先拆
uv pip install unsloth
解析 100 個套件,下載 32 個,98 秒。沒有任何一款需要 nvcc 現場編譯。裝出來的組合是:
| 套件 | 版本 |
|---|---|
| unsloth / unsloth-zoo | 2026.9.2 / 2026.9.1 |
| torch | 2.11.0+cu130 |
| bitsandbytes | 0.50.2(有 aarch64 現成輪子) |
| transformers / trl / peft | 5.5.0 / 0.24.0 / 0.20.0 |
| datasets / triton | 4.3.0 / 3.6.0 |
| xformers | 不在相依樹裡 |
| torchcodec | 不在相依樹裡 |
第一,bitsandbytes 已經有 aarch64 輪子,這是過去在 ARM 上最常見的那道牆,現在沒了,不必準備 cmake -DCOMPUTE_BACKEND=cuda 那套備案。
第二,xformers 與 torchcodec 根本不是 unsloth 2026.9 的相依,我為它們準備的解 pin 方案完全沒派上用場,Day 6 那個預告在今天的版本上已經過期。
第三,transformers 已經走到 5.x,unsloth 跟得上,不需要往回釘版本。
import 全部模組 4.8 秒,GB10 被正確認成 sm_121。Unsloth 的啟動橫幅有個顯示瑕疵,它印 CUDA: 12.1,那其實是 compute capability (12, 1) 被當成版本號,同一行後面的 CUDA Toolkit: 13.0 才是對的。橫幅也老實寫著 FA [Xformers = None. FA2 = False],走的是 torch 原生 SDPA。
驗證環境的最小腳本,載入一款小模型跑一步:
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"/mnt/nas/AIModels/huggingface/Qwen3.8-27B", load_in_4bit=True, max_seq_length=4096)
載入時間 558.0 秒(從 NFS 讀 52 GB 的 BF16 權重),載入後系統記憶體消耗 17.5 GB,其中 torch 配置量 16.74 GB。之後把權重 rsync 到本機 NVMe,同一份模型載入降到 416 秒,暖快取之後 322.7 秒。rsync 本身很快,55.6 GB 在 36 秒內落地,1.40 GB/s。
4-bit 載入 27B 約 17 GB,加上 LoRA 參數、梯度、優化器狀態與 activation,統一記憶體的餘裕讓 batch size 開得比一般 NVIDIA 獨立顯示卡大方。但這個「大方」實際上換到了什麼呢? 第三節會給出跟直覺相反的答案。
一、page cache 會讓 GPU 看起來記憶體不足。 把 52 GB 權重 rsync 到本機之後,free 顯示 available 還有 116 GB,但真正空著的只有 14 GB,另外 103 GB 卡在 page cache。統一記憶體下 torch.cuda.mem_get_info() 只回報 13.4 GB free,因為可回收的快取不算數。accelerate 的自動裝置規劃器據此判定模型放不下,把幾層丟到 CPU,bitsandbytes 立刻拒絕:
ValueError: Some modules are dispatched on the CPU or the disk.
這個錯誤訊息會讓人以為記憶體真的不夠,其實是快取的假象。解法是跳過規劃器:
model, _ = FastLanguageModel.from_pretrained(
BASE, load_in_4bit=True, max_seq_length=8192, dtype=None,
device_map={"": 0}) # 不是最佳化,是必要
二、Qwen3.8-27B 是多模態架構。 它的類別是 Qwen3_5ForConditionalGeneration,FastLanguageModel.from_pretrained 回傳的第二個值是 Qwen3VLProcessor 而不是純 tokenizer。把它交給 TRL 的 processing_class,TRL 會把你的提示詞字串當成圖片來源:
ValueError: Incorrect image source. Must be a valid URL starting with
`http://` or `https://` ... Got <|im_start|>sy
純文字訓練要自己用 AutoTokenizer.from_pretrained(BASE) 換掉它。
微調的成敗七成在資料。今天的資料集分兩部分,全部繁中、全部機器可驗。開工前先把 16 篇定稿切成訓練 12 篇、保留 4 篇,保留組完全不進訓練,之後三項驗收都在它上面做,這樣 PPL 那一列才不會是自己測自己。
| 組別 | 篇數 | 字數 | 散文段 |
|---|---|---|---|
| 訓練 | 12 | 155,303 | 326 |
| 保留 | 4 | 38,540 | 86 |
答案出乎意料,而且直接改寫了資料生成的做法。
我開了 Ornith-1.5-35B-A3B NVFP4 當生成器,十道題開思考模式,用簡體字偵測器與語言判定量它的思考區塊:
| 條件 | 思考語言分布 | 思考是繁中 |
|---|---|---|
| 裸問 | 英文 9、混合 1 | 0 / 10 |
| 加上「思考必須全程繁體中文」的 system prompt | 英文 7、混合 1、繁中 1、簡體 1 | 1 / 10 |
Ornith 思考時用英文,不是簡體也不是繁體,答案才切回繁中。而且強制提示詞幾乎壓不動它,加了規則之後還有一題整段掉進簡體。這跟 Day 14 在 Qwen3.8-27B 上看到的(思考用簡體)是不同的病,但結論一樣:繁中思考的示範資料沒辦法用「採集模型原生推理軌跡」的方式取得。
所以做法改成「撰寫」而不是「採集」,請模型把繁中推理過程當成輸出內容產生,用嚴格的 <思考>...</思考><答案>...</答案> 格式包起來再解析。我們要的是訓練目標文本,不是它的原生軌跡,這樣做在資料品質上完全成立,而且產出可控。

Day 14 到 Day 15 的考卷用的是一張手工維護的簡體字表,在註解裡寫明「這台機器沒有 opencc/zhconv,所以覆蓋率不是 100%」。今天發現 opencc-python-reimplemented 是純 Python,aarch64 直接裝得起來,於是改用 OpenCC 逐字 s2t 判定,一個字轉換後變了,它就是簡體專用字。同一句測試字串,手工表抓到 6 個,OpenCC 抓到 9 個。
還補了一個「語言判定」,因為只數簡體字會出事,比方說一段全英文的思考,簡體洩漏是 0,會被誤判成乾淨的繁中。判定用漢字數對拉丁單字數的比例,不是對字母數,否則一段中文推理裡出現 search_files 或 /var/log 就會被算成英文。
四類題目各 125 筆,改寫、摘要、推理、工具呼叫。送出 1000 題,思考區塊與答案都收下來,思考區塊也要進訓練資料,因為目標是改變它思考的語言,不只是答案的語言。
第一版的良率很難看,600 題只留下 158 筆,被擋掉的 339 筆全都掛在「思考不是繁中」。翻開來看才發現這些不是英文,是繁體中文夾雜少數簡體字,像「LLM 推理优化」這種。它們是差一點就合格的樣本。加上一步 OpenCC 修復(s2t 只會動簡體專用字,帶詞彙上下文,對本來就是繁體的文字不會誤改),良率立刻不一樣:
| 指標 | 嚴格版 | 加修復 |
|---|---|---|
| 送出 | 600 | 1000 |
| 需要修復 | 不修 | 610 |
| 思考不是繁中而丟棄 | 339 | 26 |
| 答案有簡體而丟棄 | 39 | 25 |
| 保留 | 158 | 500 |
| 耗時 | 1103 秒 | 1901 秒 |
十筆裡有六筆需要修簡體字,這個比例本身就是今天要微調的理由。
從訓練 12 篇取散文段落,做成「把這段改寫成符合規範的版本」的指令對。原文由模型生成一個帶破折號、分號與「前沿」的變體當輸入,將我的定稿當輸出,採用反向生成而不是正向生成,是因為定稿才是黃金標準。
進行流程前,需要多一步清洗,把含違規的段落先讓模型依規範改寫,改完程式斷言三條規則零違規,不合格就丟掉不入資料級。這邊要提到一個概念就是之前講過的 GIGO,也就是垃圾進垃圾出,你給 AI 怎樣的資料品質,會影響到他的輸出成果。
| 階段 | 數字 |
|---|---|
| 訓練段落 | 326 |
| 含違規需清洗 | 45 |
| 清洗失敗丟棄 | 21 |
| 清洗後 gold | 305 |
| 反向生成失敗 | 101 |
| 第一輪產出 | 204 對 |
| 補一輪(指令更明確並附範例) | 再加 24 對 |
| 最終 | 228 對 |
這是反向生成這個做法的一個真實限制,「frontier 譯為尖端或先進」這條規則,在我自己的語料裡幾乎不存在。訓練段落出現「領先」8 次、「先進」1 次、「最新」1 次、「前沿」0 次,而保留組四個詞全部是 0。反向生成器沒有素材可以把它變成違規,結果是 228 筆訓練輸入裡只有 4 筆含「前沿」,65 筆測試輸入裡是 0 筆。
所以這條規則今天既訓不到也測不到,計分只能縮到破折號與分號兩條。反向生成只能教會你的語料真的有在用的規則。
資料集合計 728 筆,token 長度中位數 363、p90 是 651、最長 1400。格式走 ChatML,存進 /mnt/nas/AIModels/datasets/,那個目錄是 Day 5 建模型庫時留下的位子,到今天為止一直是空的。
設定的思路是先保守跑通再放大:
model = FastLanguageModel.get_peft_model(
model, r=16, lora_alpha=32,
target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
lora_dropout=0, use_gradient_checkpointing="unsloth")
學習率 2e-4、cosine、warmup 5%、1 epoch、per_device_train_batch_size=4、gradient_accumulation_steps=2、max_length=1536、optim="adamw_8bit",損失只算在 completion 上,prompt 被遮罩。726 筆樣本、91 個 optimizer step。
我原本想掃一輪 (batch, seq_len) 找上限,用真實資料跑,得到這張表:
| bs | max_length | 每步秒數 | torch 峰值 GB | 系統已用 GB |
|---|---|---|---|---|
| 1 | 2048 | 16.17 | 17.81 | 27.35 |
| 2 | 2048 | 7.12 | 18.27 | 28.42 |
| 4 | 2048 | 8.74 | 19.68 | 30.89 |
| 1 | 4096 | 2.93 | 17.82 | 30.87 |
| 2 | 4096 | 4.29 | 18.28 | 30.83 |
| 4 | 4096 | 8.30 | 19.68 | 30.07 |
| 1 | 8192 | 2.94 | 17.82 | 30.10 |
| 2 | 8192 | 4.29 | 18.28 | 30.05 |
這張表幾乎每個數字都不能用,原因如下。
第一,bs=1, seq=2048 那格的 16.17 秒吸收了一次性的 kernel 暖機成本,同一份資料在 bs=2 只要 7.12 秒。掃描的第一格永遠是暖機格,要嘛先跑一輪丟掉,要嘛就別讀它。
第二,也是更根本的:max_length 是截斷上限,不是填充長度。 我的訓練樣本 token 中位數只有 363、p90 是 651、最長 1400,所以把 max_length 設成 8192 之後,沒有任何一批真的變成 8192 長,記憶體當然一動也不動(17.82 GB 對 17.81 GB)。這張表看起來像是在量序列長度的天花板,實際上八格跑的是同一件事。
要量真正的上限,必須送真的那麼長的合成序列進去:
| bs | seq | 每步秒數 | tok/s | torch 峰值 GB | 系統已用 GB |
|---|---|---|---|---|---|
| 1 | 2048 | 8.22 | 249.2 | 19.21 | 28.19 |
| 4 | 2048 | 28.80 | 284.5 | 20.17 | 36.31 |
| 8 | 2048 | 59.10 | 277.2 | 22.87 | 52.60 |
| 16 | 2048 | 174.77 | 187.5 | 28.29 | 85.29 |
這才是今天最有價值的一張表,而且結論跟直覺相反:加大 batch 完全換不到吞吐量的效益。 從 1 到 8,每步時間幾乎線性成長,tok/s 卡在 250 到 285 之間不動。推到 16 反而掉到 187.5。記憶體倒是老實地從 28 GB 漲到 85 GB。
原因就是 Day 2 我已經講過的那件事,這台機器是頻寬受限。訓練的每一步都要把權重讀過一遍再把梯度寫回去,273 GB/s 是硬牆,batch 開再大也只是讓同一份權重服務更多樣本,攤不掉那個讀寫成本。
所以統一記憶體在訓練上買到的絕非速度,而是「不用卸載」。你可以把 batch 開到獨顯會直接 OOM 的尺寸,程式不會崩,但也不會變快。
這一輪還額外讓機器也出代價,當自動腳本掃到 bs=16 之後的下一格是 bs=32,那需要大約 150 GB,超過這台的 121 GB。機器沒有被 OOM killer 收掉,而是整台停擺,ping 有回應但 SSH 完全連不上,最後是手按電源鍵重開的。事後在日誌裡能看到停擺前 43 分鐘 NVRM 就開始噴 NV_ERR_NO_MEMORY,那是還來得及收手的警告,可惜我來不及看到,只好先關機再開起來。
| 項目 | 數值 |
|---|---|
| 設定 | bs=4、ga=2、max_length=1536、r=16 |
| 樣本 | 726 筆 |
| 訓練 token | 416,753 |
| 步數 | 91 |
| 一個 epoch 耗時 | 2,241.5 秒(37.4 分鐘) |
| 訓練吞吐 | 185.9 tok/s |
| torch 峰值 | 21.72 GB |
| 系統峰值 | 38.43 GB |
| loss(首 / 末 / 平均) | 0.7735 / 0.6001 / 0.7144 |
37 分鐘訓完一個 27B 模型的 LoRA,峰值只吃掉 121.6 GB 裡的 38.4 GB,三分之二的記憶體從頭到尾閒著。這就是這台機器在這一級模型上的體感:不快,但很寬鬆。

如果看這張 nvtop 的記憶體截圖,結果 GB10 上 NVML 對統一記憶體回報 N/A,nvtop 的記憶體欄位是空的。真正可信的記憶體證據只能從 /proc/meminfo 每五秒抽一次,那張時間軸圖就是這樣來的。
訓練曲線也西澳說明一下,loss 在 warmup 期間先升到 1.0 附近,接著在前三分之一掉到 0.65 左右,之後就在 0.68 上下橫著不動了。這不是過擬合(沒有回升),而是一個 epoch、726 筆資料能給的訊號就這麼多。要再往下走得靠更多資料或更多輪,今天先不追。
微調完的 adapter 先不合併,用 Unsloth 直接推論做驗收。before 與 after 走完全相同的路徑:同一支 shim、同一份 4-bit 底模、同一組貪婪解碼參數,差別只有掛不掛 adapter。這一點很重要,Day 15 已經證實 vLLM 上的貪婪解碼不可重現,所以我沒有沿用舊的 20/20,而是重新量了一次 before。

考卷與工具呼叫兩項直接重用 Day 15 那份 repo 的 exam.py,一個字都沒改,只是在本機起一個 OpenAI 相容端點餵給它。
| 指標 | 微調前 | 微調後 |
|---|---|---|
| 考卷繁中十題 | 20 / 20 | 20 / 20 |
| 工具呼叫十輪 | 10 / 10 | 10 / 10 |
| 多步 agent 三題 | 3 / 3 | 3 / 3 |
| 思考產出完整區塊(100 題) | 27 / 100 | 99 / 100 |
| 其中思考是繁中 | 16(59.3%) | 83(83.8%) |
| 其中思考是簡體 | 10(37.0%) | 16(16.2%) |
| 思考區塊簡體洩漏字數 | 652 | 30 |
| 編輯規範違規(65 段,提示詞含規則) | 5(乾淨 61/65) | 1(乾淨 64/65) |
| 繁中 PPL(保留組,未參與訓練) | 16.5473 | 16.5078 |
| decode | 10.08 tok/s | 9.65 tok/s |
先講測試本身的兩件事,不然數字會被誤讀。
PPL 這一列不能跟 Day 12 的 11.1337 比。 那個數字是 llama.cpp 跑 GGUF、在一份 254 KB 的語料上量的,今天是 transformers 走 bitsandbytes 4-bit、在四篇保留文章(10,637 token、21 個 chunk)上量的。兩條路徑不同、語料不同,只有「今天的前對今天的後」這個比較成立。而且必須用保留組,因為 Day 12 那份語料本身就是我自己的文章,拿它來考一個剛用我的文章訓練過的模型,PPL 下降只證明它背過答案吧。
思考那三列的分母會動。 探針給 900 token 的預算,微調前有 73 題的思考在預算內講不完,沒吐出 </think>,所以「完整區塊」只有 27 題,所以比率是在這 27 題上算出來的。
微調前 27 題完整思考裡有 16 題是繁中、10 題是簡體。微調後 99 題裡 83 題繁中、16 題簡體。簡體率從 37.0% 降到 16.2%,簡體洩漏字數從 652 個掉到 30 個。
但真正讓我意外的是分母:27 到 99。微調前有四分之三的題目思考講不完,微調後只剩一題。資料集 A 的思考區塊我限定在 120 到 400 字,模型把這個長度習慣也學走了。它不只換了思考的語言,還把思考收斂了。這是我沒有設計到、但事後看很合理的副作用。
有給規則時,違規從 5 降到 1、乾淨從 61/65 升到 64/65。看起來成功,但這一格從一開始就沒什麼空間:底模光靠提示詞就已經 61/65 乾淨,把輸入端 129 個破折號全部改掉,只漏了 5 個分號。

所以我補了一題真正該問的:把規則從提示詞裡拿掉,只說「請把這段潤飾得更通順」,它還守不守規範呢?
| 不給規則,65 段 | 微調前 | 微調後 |
|---|---|---|
| 違規總數 | 98 | 143 |
| 其中破折號 | 54 | 102 |
| 其中分號 | 44 | 41 |
| 零違規的輸出 | 19 / 65 | 7 / 65 |
微調後比微調前更糟。 這不是雜訊,是資料設計的直接後果,資料集 B 的每一筆,system prompt 裡都寫著那三條規則。地端 AI 模型學到的是「看到這組規則就照做」,而不是「寫文章不要用破折號」。規則被綁在提示詞上,變成一個條件反射,不是習慣。
而且沒有規則時它更傾向保留輸入的原樣(潤飾而非改寫),所以輸入端那 129 個破折號被留下了 102 個,比底模留下的 54 個還多。
這個結果比「兩個目標都達成」有用得多。要讓模型內建一條規則,訓練資料裡必須有「沒告訴它規則、它自己做對」的樣本。 我的資料集裡一筆都沒有,自然就不會成功。
考卷 20/20、工具呼叫 10/10、多步 agent 3/3,三項都沒退步。PPL 從 16.5473 微降到 16.5078,在同一條路徑上是持平偏好,沒有變笨。decode 從 10.08 掉到 9.65 tok/s,那是 LoRA 額外的矩陣乘法,合併回底模之後就沒有了,明天會繼續完善它。
一、Unsloth 在 GB10 上已經可用
Day 6 我擔心的是 ARM64 的 wheel 相依,torchcodec 要解 pin、bitsandbytes 要用 nvcc 從原始碼建、xformers 大概沒有輪子。今天實測,這三個都不需要擔心,uv pip install unsloth 98 秒結束、零編譯,bitsandbytes 有現成的 aarch64 輪子,另外兩個根本不在相依樹裡。取而代之的是兩個統一記憶體特有的問題,page cache 會讓 torch.cuda.mem_get_info 回報 13.4 GB 而觸發 CPU 卸載,以及這款模型是 ForConditionalGeneration、unsloth 回傳 processor 而讓 TRL 把提示詞當圖片。框架的成熟度問題已經從「能不能裝」變成「懂不懂這台機器到底能幹嘛?」。

二、統一記憶體對微調的價值是寬鬆,不是速度,而且它沒有安全網。 合成定長序列的實測很乾脆,batch 從 1 開到 8,每步時間幾乎線性成長,吞吐量卡在 250 到 285 tok/s 完全不動,開到 16 還會掉到 187.5。訓練跟推論一樣是頻寬受限,每一步都要把權重讀過一遍,batch 大小攤不掉那個成本。真正的紅利在另一邊,整場訓練峰值只吃掉 121.6 GB 裡的 38.4 GB,你不必研究任何卸載技巧,27B 的 LoRA 就是能跑,跑 37 分鐘一個 epoch。代價是超過實體記憶體時它不會優雅降級,我把 batch 推到需要 150 GB 那一格,整台機器停擺到只剩 ping 有回應,最後用電源鍵重開。如果是 NVIDIS 獨立顯卡的電腦會給你一個 OOM 例外,系統就還給你了,但統一記憶體碰到這種情況會給你一台當掉的電腦。
三、用自己的規範反向生成資料,可以推廣,但有兩個必要條件。
今天兩個目標一個成一個敗,敗的那個很有教育意義。繁中思考成功,簡體洩漏從 652 個字降到 30 個,還意外把思考長度收斂了。編輯規範表面上也成功,違規從 5 降到 1,但那是假的,底模光靠提示詞就已經 61/65 乾淨,這一格從頭就沒空間。把規則從提示詞拿掉再測,微調後反而從 98 個違規惡化到 143 個。原因是資料集裡每一筆的 system prompt 都寫著那三條規則,模型學到的是條件反射不是習慣。
所以這個做法要能用,你拿來訓練的資料集必須滿足兩件事,規則要在你的語料裡真的被使用(我那條「frontier 譯尖端」在保留組出現 0 次,訓不到也測不到),以及必須有「沒告訴它規則、它自己做對」的樣本(我一筆都沒有)。有內部風格規範的團隊可以照抄這個流程,但這兩個條件要先過。
adapter 訓練好了,明天讓它上線,會合併回底模、轉 GGUF、走 Day 12 的量化階梯選一檔、掛進 Day 18 的閘道,變成 zh-writer 那個用途名背後的新模型。也正是我們進行地端 AI 模型微調的最後一哩路,從 checkpoint 到服務端點之路。
我們 Day 26 見囉。
Day 1|為什麼 2026 年是地端 AI 部署元年:系列規劃與硬體總覽
Day 2|DGX Spark GB10 深度解析:128GB 統一記憶體到底解決了什麼問題
Day 3|網路與儲存規劃:10GbE 骨幹、雙 Spark 直連與 NFS 集中模型庫
Day 4|開箱之後:DGX OS 初始環境建置與 CUDA、Docker 生態確認
Day 5|NFS 模型庫實戰:下載工具、權限設計與版本管理
Day 6|llama.cpp、vLLM、TensorRT-LLM 、Ollama、DS4 與 Unsloth 的定位與取捨
Day 7|第一個模型上線:gpt-oss-120b 從模型庫到 API 的完整流程
Day 8|llama.cpp 實測:先定量尺與 SOP
Day 9|vLLM 實測:官方容器、同時處理吞吐曲線,與剛出爐的新模型 Ornith 1.5
Day 10|TensorRT-LLM 實測:我花了一個下午,跟它的預設值搏感情
Day 11|地端 AI 三大推論引擎綜合評測,同場加映神秘嘉賓
Day 12|量化格式解析:「4-bit」兩個字,古今多少事 ? 都付笑談中
Day 13|Perplexity 把整套 Agent 搬上 DGX Spark:地端元年的官方背書
Day 14|Qwen3.8-27B 與 Flash-Next 加碼實測:地端模型世代對決與落地評估
Day 15|Ornith 1.5 實測:為 AI agent 而生的 35B-A3B
Day 16|模型選型方法論:五個問題幫你跳脫排行榜迷失,找到適合自己任務用的 AI 模型
Day 17|ds4 深度解析:Redis 之父只為一種模型造了一個引擎
Day 18|OpenAI 相容 API 層:地端 AI 多模型常駐好助手
Day 19|地端 agent 框架總覽:它們到底對你的端點做了什麼呢?
Day 20|DeepSeek Harness 實戰:21 萬顆星的外掛式 harness 接上地端
Day 21|herdr 是地端 AI 同時跑多個 AI agent 的最佳解
Day 22|語意錨點對上 prefix cache,FreeToken VS vLLM,以及三個關於統一記憶體的教訓
Day 23|ComfyUI 部署與 NFS 模型庫整合:把集中管理的哲學帶進生圖世界
Day 24|MiniMax-H3 地端影片生成:h3-ui 與四步蒸餾版的三段對決
Day 25|Unsloth 微調實戰:教一款模型用繁體中文思考,並遵守台灣的編輯規範
Day 26|微調的最後一哩路:合併、轉檔、量化、上線
Day 27|雙 DGX Spark GB10 合體與分工合作的部署與實測